home *** CD-ROM | disk | FTP | other *** search
/ Software Vault: The Gold Collection / Software Vault - The Gold Collection (American Databankers) (1993).ISO / cdr49 / cl182.zip / CL.DOC < prev    next >
Text File  |  1993-05-15  |  46KB  |  1,306 lines

  1. Container Lite (CL version 1.82:  5/14/93)
  2. A Portable Persistent Hybrid Container Class for C++
  3. Copyright 1993, John Webster Small, All rights reserved
  4. PSW / Power SoftWare, P.O. Box 10072, McLean, VA 22102
  5. (703) 759-3838
  6.  
  7. This version of Container Lite is licensed to you as shareware.  For 
  8. details, see section 5 below.
  9.  
  10.  
  11. 1.0  Introduction
  12.  
  13. Applications using Container Lite use only a small fraction of 
  14. the container code space consumed by other commercial and 
  15. compiler bundled container class libraries.  Use Container Lite 
  16. repeatedly throughout all your applications for even greater 
  17. savings in design and coding effort as well as drastically 
  18. reducing their sizes.  Container Lite is ideal for coding games, 
  19. rolling your own application frameworks, and any application 
  20. requiring user configurations to persist.
  21.  
  22. Container Lite is a portable, persistent, hybrid container class 
  23. library compatible with compilers supporting AT&T's C++ 2.x, 3.x 
  24. standard.  Container Lite is based upon a single container class 
  25. having a hybrid stack, queue, deque, list, array interface and 
  26. built-in sort, search, and iterate member functions.
  27.  
  28.  
  29. 1.1  Implementation
  30.  
  31. Container Lite is implemented as a dynamically sized array of void 
  32. pointers, pointing to the data you are collecting.  Template 
  33. invocations additionally define primitives for on the fly copy 
  34. initialization, assignment, and destruction of your data nodes 
  35. automatically.  Container Lite's "form template" provides the same 
  36. automatic code generation and strong type checking for compilers not 
  37. yet supporting templates.
  38.  
  39.  
  40. 1.2  Installation
  41.  
  42. Copy the following Container Lite files to the appropriate development 
  43. directory for your C++ compiler if you have not already done so.
  44.  
  45.     cl.doc        this file
  46.     cl.hpp        Container Lite header
  47.     cl.cpp        Container Lite source
  48.  
  49.     cl.hpt        Container Lite template
  50.     cl.hpf        Container Lite form template
  51.  
  52.  
  53. 2.0  Hybrid Container
  54.  
  55. Always think of Container Lite in terms of its multi-faceted hybrid
  56. behavior.  One moment your container is a stack, the next a
  57. priority queue or array, etcetera.  With Container Lite you are
  58. never locked into some arcane branch class of a convoluted,
  59. towering hierarchy like you would be with a conventional "textbook"
  60. container implementation.
  61.  
  62.  
  63. 2.1  Elastic Array
  64.  
  65. Container Lite's elastic array behavior provides the foundation for
  66. the rest of its inherent behaviors.  Imagine an array in which you
  67. can insert or extract cells at any location.  Container Lite lets
  68. you specify the granularity and hysteresis of this elasticity in
  69. constructor and/or housekeeping calls with a delta parameter.  In
  70. other words, when a new cell needs to be added how many additional
  71. spare cells should be provided to optimize future expansion
  72. efforts?  And how many spare cells should be allowed to accumulate
  73. before compaction?  The following list of member functions catalogs
  74. this dynamic array behavior.
  75.  
  76.      vector()            Limit()             setLimit()
  77.      pack()              Delta()             setDelta()
  78.      Nodes()             MaxNodes()          setMaxNodes()
  79.      vacancy()           vacancyNonElastic()
  80.      atIns()             atInsNew()          atRmv()
  81.      allRmv()            atDel()             atDelAsg()
  82.      allDel()            atPut()             atPutNew()
  83.      atPutAsg()          atGet()             operator[]()
  84.      atGetAsg()          atXchg()            index()
  85.      forEach()
  86.  
  87. If you use Container Lite's template or "form template" to generate
  88. a strongly typed checked container wrapper, you can then assign and
  89. clone your data on the fly.  Primitives with "Asg" and "New"
  90. suffixes assign and clone your data respectively.  Templates can
  91. also provide persistence if your data has overloaded stream
  92. insertion and extraction operators along with a default
  93. constructor.  If not you still have the option of defining template
  94. mediator functions or accepting binary defaults.
  95.  
  96. Elastic array operation is really summed up by two primitives,
  97. atIns() and atRmv().  AtIns() writes a pointer value to a newly
  98. inserted cell in the container's logical array at the index
  99. indicated.  The cell originally at the index and all successive
  100. cells are pushed back one index location, expanding the physical
  101. array if necessary.  AtRmv() performs the inverse function of
  102. atIns(), extracting the indexed cell returning its pointer while
  103. pulling all successive cell indices forward by one.  The physical
  104. array is shrunk automatically if excessive space has accumulated.
  105.  
  106. If you use the Container Lite template to generate a strongly typed
  107. check wrapper class for your data, you can then use the above
  108. primitives with "Asg", "New", and "Del" suffixes.  These primitives
  109. allow you to assign, clone, and delete your data nodes on the fly.
  110. Persistence is also provided which we will see in our first
  111. example.
  112.  
  113.      // examp201.cpp -- link with cl.obj
  114.  
  115.      #include <stdlib.h> // rand(), srand()
  116.      #include <time.h>   // time_t, time()
  117.      #include "cl.hpt"
  118.  
  119.      #define intFile "ints.tmp"
  120.  
  121.      main()
  122.      {
  123.           time_t t;
  124.           srand((unsigned) time(&t));
  125.           CL<int> ci(CL_DANDS,15);
  126.           int i = 0;
  127.           while (ci.atInsNew(rand()%(ci.Nodes()+1),&i))
  128.                i++;
  129.           ci.save(intFile);
  130.           ci.allClr();
  131.           ci.load(intFile);
  132.           cout << "0-14 in random order: " << endl;
  133.           while (ci.atDelAsg(0,&i))
  134.                cout << i << endl;
  135.           return 0;
  136.      }
  137.  
  138. CL<int> generates a wrapper class for integers.  The CL_DANDS
  139. composite flag enables the container's "(A)sg", "(N)ew", "(D)el"
  140. and stream (S)torage primitives.  Actually CL_DANDS is defined as
  141. the flags expression: CL_DASSIGN | CL_DNEW | CL_DDELETE |
  142. CL_DSTORE. The numbers zero through fourteen are inserted at random
  143. locations in the "array."  AtInsNew() performs the same operation
  144. as atIns() except the data is cloned on the fly and the clone is
  145. bound in the container instead of the variable i.  In either case
  146. a pointer to the bound node is returned to indicate success or NULL
  147. otherwise.  AtDelAsg() basically performs an atGetAsg(),
  148. (As)si(g)ning (copying) the outgoing node to the variable i, before
  149. performing an equivalent atDel() which is the same thing as an
  150. atRmv() except the outgoing node is (Del)eted and not just the cell
  151. (R)e(m)o(v)ed from the array.
  152.  
  153. If your compiler doesn't support templates you can use the "form 
  154. template" approach shown below instead.
  155.  
  156.      // examp202.cpp -- link with cl.obj
  157.  
  158.      #include <stdlib.h> // rand(), srand()
  159.      #include <time.h>   // time_t, time()
  160.  
  161.      #define   ITEM  int
  162.      #define   CL    CL_int
  163.      #include "cl.hpf"
  164.  
  165.      #define intFile "ints.tmp"
  166.  
  167.      main()
  168.      {
  169.           time_t t;
  170.           srand((unsigned) time(&t));
  171.           CL_int ci(CL_DANDS,15);
  172.           int i = 0;
  173.           while (ci.atInsNew(rand()%(ci.Nodes()+1),&i))
  174.                i++;
  175.           ci.save(intFile);
  176.           ci.allClr();
  177.           ci.load(intFile);
  178.           cout << "0-14 in random order: " << endl;
  179.           while (ci.atDelAsg(0,&i))
  180.                cout << i << endl;
  181.           return 0;
  182.      }
  183.  
  184. Notice that you must define ITEM as the parameter type that would
  185. normally be passed as the template parameter, i.e. "#define ITEM
  186. int" is the equivalent of passing "int" to "CL<>."  Likewise CL is
  187. defined as a new type name so that you use "CL_int" in place of
  188. "CL<int>."
  189.  
  190. The next example shows a container being used to sort a standard
  191. C++ vector of strings.  Both the template and form template
  192. approaches are shown.
  193.  
  194.      // examp203.cpp - link with cl.obj
  195.  
  196.      // #define CPP_TEMPLATES
  197.  
  198.      char *V[] = {
  199.           "Vectors of pointers can",
  200.           "be exploded into containers",
  201.           "for processing.  A container",
  202.           "can also be imploded into",
  203.           "a vector of pointers to",
  204.           "its nodes.",
  205.           0
  206.      };
  207.  
  208.      #if defined(CPP_TEMPLATES)
  209.           #include "cl.hpt"
  210.           ITEM_str
  211.      #else
  212.           #define   ITEM_str
  213.           #include "cl.hpf"
  214.      #endif
  215.  
  216.      #define vectorFile "vector.tmp"
  217.  
  218.      main()
  219.      {
  220.           CL_str v(V,0,CL_DSTORE);  // explode constructor
  221.           cout << "\n\nExploded, unsorted vector ...\n\n";
  222.           for (unsigned i = 0; v[i]; i++)
  223.                cout << (char *) v[i] << "\n";
  224.           v.save(vectorFile);
  225.           v.allClr();
  226.           v.load(vectorFile);
  227.           v.sort ();    // sort
  228.           v.vector(V);  // implode
  229.           cout << "\n\nImploded, sorted vector ... \n\n";
  230.           for (i = 0; v[i]; i++)  
  231.                cout << v[i] << "\n";
  232.           return 0;
  233.      }
  234.  
  235. The vector() function will allocation a dynamic vector if one isn't
  236. provided as a parameter.  The overloaded subscript operator, i.e.
  237. [], is a convenient notation for calling atGet().  For details
  238. about any Container Lite member function or data be sure to refer
  239. to the reference chapter.  Remember, the examples given in this
  240. chapter are meant to give you a feel for Container Lite's different
  241. modes of operation, i.e. elastic-array, stack-queue-deque, list,
  242. and sort-search-unique.  Thus the descriptions here don't focus on
  243. member function details.
  244.  
  245. You may have noticed by now that neither iostream.h nor iomanip.h
  246. is ever included in our examples.  That's because Container Lite
  247. header files already pull them in.  And string.h is automatically
  248. pulled in by the (form) template header.
  249.  
  250.  
  251. 2.2  Stack-Queue-Deque
  252.  
  253. A stack provides LIFO (Last In, First Out) storage.  Container Lite
  254. has a complete set of stack primitives.  These functions don't
  255. disturb the list behavior of a container, e.g. current node
  256. setting.  A queue provides FIFO (First In, First Out) storage.  And
  257. the deque (pronounced deck) provides FOLO (First Out, Last Out)
  258. storage.  Think of a deque as a combination stack-queue with the
  259. additional capability of being able to be popped from the rear. 
  260. The following is a list of the member functions supporting this
  261. stack-queue-deque behavior:
  262.  
  263.           push()              pushNew()           pop()
  264.           popDel()            popDelAsg()         top()
  265.           topAsg()            insQ()              insQNew()
  266.           unQ()               unQDel()            unQDelAsg()
  267.           rear()              rearAsg()           operator<<()
  268.           operator>>()
  269.  
  270. The example for this section uses a container of floats.
  271.  
  272.      // examp204.cpp -- link with cl.obj
  273.  
  274.      // #define CPP_TEMPLATES
  275.  
  276.      #if defined(CPP_TEMPLATES)
  277.           #include "cl.hpt"
  278.           #define CL_float CL<float>
  279.      #else
  280.           #define   ITEM  float
  281.           #define   CL    CL_float
  282.           #include "cl.hpf"
  283.      #endif
  284.  
  285.      #define floatFile "floats.tmp"
  286.  
  287.      main()
  288.      {
  289.           CL_float cf(CL_DANDS,10);
  290.           for (float i = 1.0; cf.pushNew(&i); i++);
  291.           cf.save(floatFile);
  292.           cf.allClr();
  293.           cf.load(floatFile);
  294.           cout << "Count to 10: " << endl;
  295.           while (cf.unQDelAsg(&i))
  296.                cout << i << endl;
  297.           return 0;
  298.      }
  299.  
  300. CL_DANDS (equivalent to the CL_DASSIGN, CL_DNEW, CL_DDELETE, and
  301. CL_DSTORE flags) is set in the constructor call this time.  Without
  302. CL_DNEW being set, pushNew() would have been inhibited and all
  303. other member functions with the "New" suffix.  Without CL_DASSIGN
  304. and CL_DDELETE being set, unQDelAsg() would also have been
  305. inhibited along with all other functions with "Asg" and/or "Del" in
  306. the names.  Without CL_DSTORE the save() primitive would also have
  307. been inhibited.  The various flags are explained in detail in the
  308. reference chapter.
  309.  
  310. This container is limited to a maximum of 10 items.  That's because
  311. 10 was passed to the constructor parameter named "maxNodes."  This
  312. can be changed at any time with a call to the setMaxNodes()
  313. function that you saw cataloged in the previous section.  The unQ()
  314. family of member functions work on the rear of the queue thus
  315. providing deque behavior.
  316.  
  317. The container's destructor is automatically called just before
  318. "return 0;."  Since the previous while loop has already deleted all
  319. nodes there is nothing left to delete.  However, you should take
  320. note that if a container's CL_DDELETE flag is raised the destructor
  321. will attempt to delete any remaining nodes, otherwise the nodes are
  322. simply removed.  Thus you should typically segregate you containers
  323. into two groups: those with static nodes and those with dynamic. 
  324. You don't want to accidently delete a statically allocated node
  325. that is mingling among the dynamically allocated ones!  If you mix
  326. the two types it's safest not to set the CL_DDELETE flag since a
  327. compiler generated destructor call could then jump out and bite
  328. you!
  329.  
  330.  
  331. 2.3  List
  332.  
  333. Any container can be treated as if it were a doubly linked list
  334. thereby providing sequential storage.  In a container's header
  335. structure a current node index is maintained that lets the list
  336. primitives know where the insertion or removal is to take place. 
  337. The dynamic-array and stack-queue-list primitives don't disturb
  338. this current node index unless of course it points to the cell
  339. that's being removed.  In that case the current node becomes
  340. undefined just like it was when the container was first
  341. constructed.  Here's a list of the list primitives.
  342.  
  343.      CurNode()      setCurNode()        ins()
  344.      insNew()       rmv()               del()
  345.      delAsg()       put()               putNew()
  346.      putAsg()       get()               getAsg()
  347.      next()         operator++()        nextAsg()
  348.      prev()         operator--()        prevAsg()
  349.  
  350. Let's look at a little more rigorous, realistic example this time. 
  351. We'll create a container of Employees.  You'll learn more in the
  352. next chapter about using (form) templates for various data types.
  353.  
  354.      // examp205.cpp -- link with cl.obj
  355.  
  356.      // #define CPP_TEMPLATES
  357.  
  358.      #include <string.h>
  359.      #include "cl.hpp"
  360.  
  361.      class Employee  {
  362.           char *name;
  363.           unsigned salary;
  364.           int cmp(const Employee& e) const;
  365.           static    Employee * THIS;
  366.           static    ostream& SHOW(ostream& os)
  367.           {
  368.              return os << "Employee name: "
  369.                 << setw(20)
  370.                 << (THIS->name? THIS->name : "n/a")
  371.                 << "\tsalary: " << THIS->salary;
  372.           }
  373.      public:
  374.           Employee(const char * name = 0,
  375.                unsigned salary = 0)
  376.           {
  377.                this->name = (name? strdup(name) : 0);
  378.                this->salary = salary;
  379.           }
  380.           Employee(const Employee& e)
  381.           {
  382.                name = (e.name? strdup(e.name) : 0);
  383.                salary = e.salary;
  384.           }
  385.           Employee& operator=(const Employee& e)
  386.           {
  387.                delete name;
  388.                name = (e.name? strdup(e.name) : 0);
  389.                salary = e.salary;
  390.                return *this;
  391.           }
  392.           int operator==(const Employee& e) const
  393.                { return !cmp(e); }
  394.           int operator>(const Employee& e) const
  395.                { return (cmp(e) > 0); }
  396.           ~Employee() { delete name; }
  397.           ostream& (*show())(ostream&)
  398.                { THIS = this; return SHOW; }
  399.  
  400.           friend ostream& operator<<
  401.                (ostream& os, Employee& e)
  402.           { return os << &e.name << endm << e.salary; }
  403.           friend istream& operator>>
  404.                (istream& is, Employee& e)
  405.           { return is >> &e.name >> nextm >> e.salary; }
  406.      };
  407.  
  408.      Employee * Employee::THIS;
  409.  
  410.      int Employee::cmp(const Employee& e) const
  411.      {
  412.           if (!name)
  413.                if (!e.name)
  414.                     return 0;
  415.                else
  416.                     return -1;
  417.           else
  418.                if (!e.name)
  419.                     return 1;
  420.                else
  421.                     return strcmp(name,e.name);
  422.      }
  423.  
  424.  
  425.      #if defined(CPP_TEMPLATES)
  426.           #include "cl.hpt"
  427.           #define   CL_Employee       CL<Employee>
  428.      #else
  429.           #define   ITEM      Employee
  430.           #define   CL        CL_Employee
  431.           #include "cl.hpf"
  432.      #endif
  433.  
  434.      #define EmployeeFile "employs.tmp"
  435.  
  436.      main()
  437.      {
  438.           CL_Employee cE(CL_DANDS);
  439.           cE.ins(new Employee("Doe, John",1000));
  440.           Employee E("Small, John",100);
  441.           cE.insQNew(&E);
  442.           cE.push(new Employee("Morgan, Maria",10000));
  443.           cE.save(EmployeeFile);
  444.           cE.allClr();
  445.           cE.load(EmployeeFile);
  446.           cE.sort();
  447.           cout << "\nEmployees in alphabetical order: "
  448.                << endl;
  449.           while (cE.nextAsg(&E))
  450.                cout << E.show() << endl;
  451.           return 0;
  452.      }
  453.  
  454. If you compiler supports templates you have the option of
  455. uncommenting the second line "#define CPP_TEMPLATES."
  456.  
  457. Thus far we have only seen strongly typed checked containers.  We
  458. could have just as easily used a typeless container which the next
  459. example demonstrates.  However we loose the use of "Asg" and "New"
  460. primitives along with persistence.  After all, how can a container
  461. copy, clone, or store data that it knows nothing about?  Likewise
  462. how can it sort and search untyped data?
  463.  
  464.      // examp206.cpp -- link with cl.obj
  465.  
  466.      #include <string.h> // strcmp()
  467.      #include "cl.hpp"
  468.  
  469.      main()
  470.      {
  471.        cl s;
  472.        s.ins("in typeless containers!");
  473.        s.ins("Heterogenous data");
  474.        s.ins("can be easily bound");
  475.        // s.CurNode() == 2
  476.        s.insNew("This string won't appear!");
  477.        s.sort(CLcmPcast(strcmp,void));
  478.        // s.CurNode() == CL_NOTFOUND != 0
  479.        while (++s)
  480.          cout << (char *)s.get() << endl;
  481.        return 0;
  482.      }
  483.  
  484. Notice that "cl" is a typeless container where as CL was used with
  485. templates.  The reason the fourth string won't appear is that
  486. neither the CL_DNEW flag was set in the constructor call nor was
  487. the protected scope Dassign() virtual function overridden during
  488. template generation to handle strings.  Notice that the sort()
  489. function takes an optional compare function pointer parameter.  The
  490. CLcmPcast() macro type casts the standard library's strcmp()
  491. function pointer to one taking constant void pointer parameters. 
  492. Many times when working with non persistent heterogeneous data it
  493. is easiest to play fast and loose with a typeless container.
  494.  
  495. When the current node is rmv()'ed or del()'ed its predecessor is
  496. made the new current node.  For example, if the 5th node is
  497. rmv()'ed then CurNode() returns 4.  However, if the current node
  498. being 0 is rmv()'ed then the new current node remains 0 until there
  499. are no more nodes and the current node then becomes undefined,
  500. which is not 0!  When the current node is undefined, CurNode()
  501. returns the constant CL_NOTFOUND.  The operation of rmv() is
  502. designed to perform the inverse function of ins() which inserts
  503. after the current node making the new node current.  An ins()
  504. followed by rmv() will leave a list in its original state.
  505.  
  506.  
  507. 2.4  Sort-Search-Unique Category
  508.  
  509. Containers can also be sorted and searched on a default or user
  510. supplied compare function.  You don't have to derive a new class
  511. for each different sort order!  Only template and "form template"
  512. generated containers have default compare functions (but only if
  513. the template parameter type has implicitly or explicitly defined
  514. relational operators or overrides the template's compare mediator
  515. function).  Please note that these primitives affect the list's
  516. current node setting.
  517.  
  518.      Sorted()            unSort()            setCmP()
  519.      CmP()               sort()              insSort()
  520.      insSortNew()        insUnique()         insUniqueNew()
  521.      findFirst()         operator[]          findNext()
  522.      findLast()          findPrev()          findAll()
  523.  
  524. With these primitives you can build bags, sets, and even
  525. dictionaries which our next example will demonstrate.  One common
  526. challenge to any programmer is to produce an internationalized
  527. version of his/her application.  The following string resource
  528. suggests one approach.  In our implementation, the first word in a
  529. string is the token/key for the remainder of the string.  To change
  530. language versions, edit the remainder of each string.  Since all
  531. token/key - translation strings are to be located together the task
  532. should be easy.
  533.  
  534.      // examp207.cpp -- link with cl.obj
  535.  
  536.      // #define CPP_TEMPLATES
  537.  
  538.      #include <ctype.h>
  539.  
  540.      #if defined(CPP_TEMPLATES)
  541.           #include "cl.hpt"
  542.           ITEM_str
  543.      #else
  544.           #define ITEM_str
  545.           #include "cl.hpf"
  546.      #endif
  547.  
  548.  
  549.      class StrReSrc : CL_str  {
  550.           static unsigned ridx;
  551.           static int cmp
  552.                (const char * S1, const char * S2);
  553.      protected:
  554.           virtual  voidCmP DcmP(voidCmP cmP)
  555.           { return (cmP? cmP :CLcmPcast(cmp,void)); }
  556.      public:
  557.           StrReSrc() : CL_str(CL_DSTORE) {}
  558.           StrReSrc(const char * filename)
  559.                : CL_str(defaultConstruct)
  560.                { (void) CL_str::load(filename); }
  561.           int load(const char * filename)
  562.                { return CL_str::load(filename); }
  563.           int save(const char * filename)
  564.                { return CL_str::save(filename); }
  565.           int add(char * S)
  566.                { return (insUnique(S)?1:0); }
  567.           const char * operator[](const char * S)
  568.           { return (findFirst(S)?&(((char *)get())[ridx])
  569.                     : "not found"); }
  570.           CL_str::allClr;
  571.           ~StrReSrc() { CL_str::destruct(); }
  572.      };
  573.  
  574.      unsigned StrReSrc::ridx;
  575.  
  576.      int StrReSrc::cmp(const char * S1, const char * S2)
  577.      {
  578.           for (ridx = 0; S1[ridx] == S2[ridx] ; ridx++)
  579.           {
  580.                if (S1[ridx] == '\0')
  581.                     return 1;
  582.                if (isspace(S1[ridx]))
  583.                     return 0;
  584.           }
  585.           if (S1[ridx] == '\0' || S2[ridx] == '\0')
  586.                return 0;
  587.           return 1;
  588.      }
  589.  
  590.      #define StrReSrcFile  "spanish.tmp"
  591.  
  592.      main()
  593.      {
  594.           StrReSrc sr;
  595.           sr.add("one uno");
  596.           sr.add("two dos");
  597.           sr.add("three tres");
  598.           sr.add("three wrong");
  599.           sr.save(StrReSrcFile);
  600.           sr.allClr();
  601.           sr.load(StrReSrcFile);
  602.           cout << "\nCount to three in Spanish: "
  603.                << endl;
  604.           cout << sr["one"] << endl;
  605.           cout << sr["two"] << endl;
  606.           cout << sr["three"] << endl;
  607.           return 0;
  608.      }
  609.  
  610. The (form) template wrapper for strings is used to derive a string
  611. resource.  The DcmP() virtual function is then overridden to make
  612. our token/key compare function the default.  Notice how the default
  613. subscript operator, i.e. operator[](), is overloaded to allow the
  614. token/key to access the translation/value.  Hence we see that a
  615. dictionary action is really defined by the supplied compare
  616. function and overloaded access function.  But recall a dictionary
  617. is essentially a set.  The add() function defined here assures us
  618. that only unique elements (token/keys) are allowed to belong to the
  619. string resource dictionary.   To become a bag the add() function
  620. would need to be changed to call insSort() instead of insUnique(). 
  621. And to determine the count of a particular bag item one would calls
  622. findAll().
  623.  
  624. Once a container is sorted, it will remain in a sorted state, as
  625. far as the container is concerned, until the compare function is
  626. changed or a new node is added without insSort???() or
  627. insUnique???().  However, it is possible for you to access a node,
  628. modifying the key value, without the container being aware that the
  629. sorted order has been spoiled.  Be sure to use UnSort() to let the
  630. container know if it is no longer sorted in these cases.
  631.  
  632. With other commercial and compiler bundled container libraries, you
  633. would have been forced to derived a new class for each different
  634. key.
  635.  
  636.  
  637. 3.0  Strong Type Checking
  638.  
  639.  
  640. Container Lite's foundation class is named "cl."  The "cl" class
  641. used by itself performs no type checking on the data being bound
  642. within.  These typeless containers are useful for quick and dirty
  643. handling of heterogeneous data types.  However, by using a template
  644. a container can be made to impose strong type checking for any type
  645. of data at compile time, thereby restricting the use of the
  646. container to a particular data type.  Even if your compiler doesn't
  647. support templates, strong type checking can still be achieved with
  648. Container Lite's "form templates."  You have already seen all three
  649. approaches in the examples of the previous chapter.
  650.  
  651.  
  652. 3.1  A Well Endowed Data Type
  653.  
  654. A fully functional container requires its nodes' data type (ITEM)
  655. to be well endowed having (either implicitly or explicitly
  656. defined):
  657.  
  658.      1. an overloaded equality operator, i.e.
  659.  
  660.           int ITEM::operator==(const ITEM&) const;
  661.  
  662.         or
  663.  
  664.           int operator==(const ITEM&, const ITEM&); 
  665.                     //friend
  666.  
  667.         and an overloaded greater than operator, i.e.
  668.  
  669.           int ITEM::operator>(const ITEM&) const;
  670.  
  671.         or
  672.  
  673.           int operator>(const ITEM&, const ITEM&);
  674.  
  675.      2. a copy initializer constructor, i.e.
  676.  
  677.           ITEM::ITEM(const ITEM&);
  678.  
  679.      3, an overloaded assignment operator, i.e.
  680.  
  681.           ITEM& ITEM::operator=(const ITEM&);
  682.  
  683.      4. an overloaded stream insertion operator, i.e.
  684.  
  685.           ostream& operator<<(ostream&,ITEM&);
  686.  
  687.      5. an overloaded stream extraction operator, i.e.
  688.  
  689.           istream& operator>>(istream&,ITEM&);
  690.  
  691.         and a default constructor, i.e.
  692.  
  693.           ITEM::ITEM();
  694.  
  695.      and
  696.  
  697.      6. a destructor if one is required, i.e.
  698.  
  699.           ITEM::~ITEM();
  700.  
  701. Let's look at a well endowed string class example.
  702.  
  703.      // examp301.cpp -- link with cl.obj
  704.  
  705.      // #define CPP_TEMPLATES
  706.  
  707.      #include <string.h>
  708.      #include "cl.hpp"
  709.  
  710.  
  711.      class String {
  712.           char *str;
  713.           size_t len;
  714.           int cmp(const String& cs) const;
  715.      public:
  716.           String(const char * s = (const char *)0)
  717.           {
  718.                str = (s? len = strlen(s), strdup(s)
  719.                     : (char *)(len = 0));
  720.           }
  721.           String(const String& s)
  722.           {
  723.                str = (((len = s.len) != 0)?
  724.                     strdup(s.str) : (char *)0);
  725.           }
  726.           ~String() { delete str; }
  727.           String& operator=(const String& s)
  728.           {
  729.                delete str;
  730.                str = (((len = s.len) != 0)?
  731.                     strdup(s.str) : (char *)0);
  732.                return *this;
  733.           }
  734.  
  735.           inline int operator==(const String& cs) const
  736.                { return !cmp(cs); }
  737.           inline int operator> (const String& cs) const
  738.                { return (cmp(cs) > 0); }
  739.           operator const char *()  { return str; }
  740.           friend ostream& operator<<(ostream& os, String& s)
  741.           { return os << &s.str; }
  742.           friend istream& operator>>(istream& is, String& s)
  743.           {
  744.                is >> &s.str;
  745.                s.len = (s.str? strlen(s.str) : 0);
  746.                return is;
  747.           }
  748.      };
  749.  
  750.      int String::cmp(const String& cs) const
  751.      {
  752.           if (!str)
  753.                if (!cs.str)
  754.                     return 0;
  755.                else
  756.                     return -1;
  757.           else
  758.                if (!cs.str)
  759.                     return 1;
  760.                else
  761.                     return strcmp(str,cs.str);
  762.      }
  763.  
  764.  
  765.      #ifdef CPP_TEMPLATES
  766.           #include  "cl.hpt"
  767.           #define    CL_String CL<String>
  768.      #else
  769.           #define    ITEM     String
  770.           #define    CL       CL_String
  771.           #include  "cl.hpf"
  772.      #endif
  773.  
  774.      #define StrFile "strings.tmp"
  775.  
  776.      main()
  777.      {
  778.           CL_String cS(CL_DANDS);
  779.           String s("can be");
  780.           cS.insNew(&s);
  781.           cS.ins(new String("tamed!"));
  782.           cS.insQ(new String("Now strings"));
  783.           cS.sort();
  784.           cS.save(StrFile);
  785.           cS.allClr();
  786.           cS.load(StrFile);
  787.           while (cS.popDelAsg(&s))
  788.                cout << (const char *)s << endl;
  789.           return 0;
  790.      }
  791.  
  792. Of course the compiler can supply a default constructor, copy
  793. initializer constructor, assignment operator, and destructor if
  794. need be.  But overloaded relational operators, and stream operators
  795. can't be inferred implicitly by the compiler for non native types. 
  796. The next example reworks examp205.cpp to allow these defaults to be
  797. meaningful, however the (form) template still needs to be told to
  798. perform binary compare and stream operations.
  799.  
  800.      // examp302.cpp -- link with cl.obj
  801.  
  802.      // #define CPP_TEMPLATES
  803.  
  804.      #include <string.h>
  805.      #include "cl.hpp"
  806.  
  807.      class Employee  {
  808.           char name[20];
  809.           unsigned salary;
  810.           static    Employee * THIS;
  811.           static    ostream& SHOW(ostream& os)
  812.           {
  813.              return os << "Employee name: "
  814.                 << setw(20)
  815.                 << (THIS->name? THIS->name : "n/a")
  816.                 << "\tsalary: " << THIS->salary;
  817.           }
  818.  
  819.      public:
  820.           Employee(const char * name = 0,
  821.                unsigned salary = 0)
  822.           {
  823.                if (name)
  824.                     strncpy(this->name,name,
  825.                          sizeof(Employee::name));
  826.                else
  827.                     this->name[0] = '\0';
  828.                this->salary = salary;
  829.           }
  830.           ostream& (*show())(ostream&)
  831.                { THIS = this; return SHOW; }
  832.      };
  833.  
  834.      Employee * Employee::THIS;
  835.  
  836.      #if defined(CPP_TEMPLATES)
  837.           #include "cl.hpt"
  838.           ITEM_BINARY_CMP_STRM(Employee)
  839.           #define   CL_Employee       CL<Employee>
  840.      #else
  841.           #define   ITEM      Employee
  842.           #define   ITEM_BINARY_CMP_STRM
  843.           #define   CL        CL_Employee
  844.           #include "cl.hpf"
  845.      #endif
  846.  
  847.      #define EmployeeFile "employs.tmp"
  848.  
  849.      main()
  850.      {
  851.           CL_Employee cE(CL_DANDS);
  852.           cE.ins(new Employee("Doe, John",1000));
  853.           Employee E("Small, John",100);
  854.           cE.insQNew(&E);
  855.           cE.push(new Employee("Morgan, Maria",10000));
  856.           cE.save(EmployeeFile);
  857.           cE.allClr();
  858.           cE.load(EmployeeFile);
  859.           cE.sort();
  860.           cout << "\nEmployees in alphabetical order: "
  861.                << endl;
  862.           while (cE.nextAsg(&E))
  863.                cout << E.show() << endl;
  864.           return 0;
  865.      }
  866.  
  867. Notice that Employee::name has been changed from a pointer into an
  868. array allowing the defaults and binary operations to be meaningful. 
  869. The ITEM_BINARY_CMP_STRM macro allowed us to configure the (form)
  870. template for Employee's deficiencies, i.e. no relation or stream
  871. operators.
  872.  
  873.  
  874. 3.2  Deficient Data Types
  875.  
  876. What do you do if your data is deficient in one or more of the six
  877. requisites of (form) templates?  One solution is to turn off
  878. dependent features, e.g. persistence, "Asg", "New", and "Del"
  879. primitives, in the wrapper generated by the (form) template.  The
  880. following macros are available for this purpose:
  881.  
  882.      ITEM_NO_REL_OPS          ITEM_NO_ASSIGN
  883.      ITEM_NO_COPYINIT         ITEM_NO_DELETE
  884.      ITEM_NO_STRM_INSERT      ITEM_NO_STRM_EXTRACT
  885.  
  886. For example, suppose that your data type is deficient in having no
  887. overloaded relational or assignment operators and that default
  888. assignment is unacceptable because of imbedded pointers.  You can
  889. use the (form) template facility to generate a strongly type
  890. checked wrapper with the follow code.
  891.  
  892.      #if defined(CPP_TEMPLATES)
  893.           #include "cl.hpt"
  894.           ITEM_NO_REL_OPS(???)
  895.           ITEM_NO_ASSIGN(???)
  896.           #define CL_??? CL<???>
  897.      #else
  898.           #define ITEM ???
  899.           #define ITEM_NO_REL_OPS
  900.           #define ITEM_NO_ASSIGN
  901.           #define CL CL_???
  902.           #include "cl.hpf"
  903.      #endif
  904.  
  905. Of course your container would have no default compare function
  906. associated with it, nor would the "Asg" primitives be operational. 
  907. Note: all the macros defined for use with Container Lite's "form
  908. template" are automatically left undefined after the inclusion of
  909. cl.hpf so that the procedure can be used repeatedly to instantiate
  910. various container types within the same file.  Additional available
  911. macros and their effects are listed below.
  912.  
  913.      ITEM_BINARY_CMP          compare data as binary blocks
  914.      ITEM_BINARY_STRM         stream data as a binary blocks
  915.      ITEM_BINARY_CMP_STRM     compare and stream data as binary
  916.                               blocks
  917.      ITEM_NO_STRM_OPS         prevent persistence
  918.      ITEM_NO_REL_STRM_OPS     no default compare, prevent
  919.                               persistence
  920.      ITEM_DELETE_ONLY         no default compare, inhibit "Asg"
  921.                               primitives, inhibit "New"
  922.                               primitives, prevent persistence
  923.      ITEM_BIND_ONLY           no default compare, inhibit "Asg"
  924.                               primitives, inhibit "New"
  925.                               primitives, inhibit "Del"
  926.                               primitives, prevent persistence
  927.  
  928. Add whatever macros you must to accurately describe the
  929. deficiencies of your data type to Container Lite's (form) template
  930. facility.  Now let's see if you were paying attention.  What would
  931. the code be to build a wrapper for strings?  The answer:
  932.  
  933.      #if defined(CPP_TEMPLATES)
  934.           #include "cl.hpt"
  935.           ITEM_DELETE_ONLY(char)
  936.           #define CL_char CL<char>
  937.      #else
  938.           #define ITEM char
  939.           #define CL   CL_char
  940.           #include "cl.hpf"
  941.      #endif
  942.  
  943. The CL_char container will have no default compare function,
  944. inhibited "Asg" and "New" primitives, and no persistence
  945. capabilities.  The only thing gained over the typeless container of
  946. examp206.cpp was strong type checking.  Hey, wait just a minute! 
  947. Back in examp203.cpp we used the following:
  948.  
  949.      #if defined(CPP_TEMPLATES)
  950.           #include "cl.hpt"
  951.           ITEM_str
  952.      #else
  953.           #define   ITEM_str
  954.           #include "cl.hpf"
  955.      #endif
  956.  
  957. and CL_str was the type name for a container of strings.  If we
  958. look in cl.hpt we will find the following code.
  959.      inline void * CL_Dnew(const char * D)
  960.           { return strdup(D); }
  961.      #define ITEM_str int CL_Dcmp            \
  962.           (const char * D1, const char * D2) \
  963.           { return strcmp(D1,D2); }          \
  964.           ITEM_NO_ASSIGN(char)
  965.      #define CL_str CL<char>
  966.  
  967. The equivalent can be gleaned from cl.hpf.  As we can see ITEM_str
  968. invokes the ITEM_NO_ASSIGN macro.  Thus the CL_str container of
  969. strings has inhibited "Asg" primitives.  But what about these
  970. CL_Dnew() and CL_Dcmp() functions?  These are (form) template
  971. mediator functions which modify the code generated to incorporate
  972. behavior not naturally found in your data type.
  973.  
  974.  
  975. 3.3  (Form) Template Mediator Functions
  976.  
  977. The (form) template automatically generates mediator functions to
  978. configure a container wrapper for your data type.  The macros
  979. outlined in the previous section are expanded into overriding
  980. mediator functions that inhibit container functionality in some
  981. form.  You can also manually override them directly to extenuate
  982. deficiencies of your data type without loss of container
  983. functionality.  Let's see a rework for a seriously deficent
  984. employee structure.
  985.  
  986.      // examp303.cpp -- link with cl.obj
  987.  
  988.      // #define CPP_TEMPLATES
  989.  
  990.      #include <string.h>
  991.      #include "cl.hpp"
  992.  
  993.      struct Employee  {
  994.           char *name;
  995.           unsigned salary;
  996.           Employee(const char * name,
  997.                unsigned salary = 0)
  998.           {
  999.                this->name = (name? strdup(name) : 0);
  1000.                this->salary = salary;
  1001.           }
  1002.           ~Employee() { delete name; }
  1003.  
  1004.           friend ostream& operator<<
  1005.                (ostream& os, Employee& e)
  1006.           {
  1007.              return os << "Employee name: "
  1008.                 << setw(20)
  1009.                 << (e.name? e.name : "n/a")
  1010.                 << "\tsalary: " << e.salary;
  1011.           }
  1012.      };
  1013.  
  1014.      int CL_Dcmp(const Employee * E1, const Employee * E2)
  1015.      {
  1016.           if (!E1->name)
  1017.                if (!E2->name)
  1018.                     return 0;
  1019.                else
  1020.                     return -1;
  1021.           else
  1022.                if (!E2->name)
  1023.                     return 1;
  1024.                else
  1025.                  return strcmp(E1->name,E2->name);
  1026.      }
  1027.  
  1028.      inline void * CL_Dassign
  1029.           (Employee * D, const Employee * S,
  1030.                unsigned /* flags */)
  1031.      {
  1032.           delete D->name;
  1033.           D->name = (S->name?
  1034.                strdup(S->name) : 0);
  1035.           D->salary = S->salary;
  1036.           return D;
  1037.      }
  1038.  
  1039.      inline void * CL_Dnew(const Employee * E)
  1040.      {
  1041.           Employee * D = new Employee(0);
  1042.           if (!D) return 0;
  1043.           D->name = (E->name? strdup(E->name) : 0);
  1044.           D->salary = E->salary;
  1045.           return D;
  1046.      }
  1047.  
  1048.      inline void CL_Ddelete(Employee * E)
  1049.      { delete E->name; E->name = 0; delete E; }
  1050.  
  1051.      inline ostream& operator<<(ostream& os, Employee ** E)
  1052.      {
  1053.           return os << &((**E).name) << endm
  1054.                << (**E).salary << endm;
  1055.      }
  1056.  
  1057.      inline istream& operator>>(istream& is, Employee ** E)
  1058.      {
  1059.           if (*E) delete *E;
  1060.           if ((*E = new Employee(0)) == 0)  {
  1061.                char *skipName = 0;
  1062.                unsigned skipSalary;
  1063.                is >> &skipName >> nextm
  1064.                     >> skipSalary;
  1065.                delete skipName;
  1066.                return is;
  1067.           }
  1068.           is >> &((**E).name) >> nextm
  1069.                >> (**E).salary >> nextm;
  1070.           return is;
  1071.      }
  1072.  
  1073.  
  1074.      #if defined(CPP_TEMPLATES)
  1075.           #include "cl.hpt"
  1076.           #define   CL_Employee       CL<Employee>
  1077.      #else
  1078.           #define   ITEM      Employee
  1079.           #define   ITEM_CMP_DEF
  1080.           #define   ITEM_ASSIGN_DEF
  1081.           #define   ITEM_COPYINIT_DEF
  1082.           #define   ITEM_DELETE_DEF
  1083.           #define   ITEM_STRM_INSERT_DEF
  1084.           #define   ITEM_STRM_EXTRACT_DEF
  1085.           #define   CL        CL_Employee
  1086.           #include "cl.hpf"
  1087.      #endif
  1088.  
  1089.      #define EmployeeFile "employs.tmp"
  1090.  
  1091.      main()
  1092.      {
  1093.           CL_Employee cE(CL_DANDS);
  1094.           cE.ins(new Employee("Doe, John",1000));
  1095.           Employee E("Small, John",100);
  1096.           cE.insQNew(&E);
  1097.           cE.push(new Employee("Morgan, Maria",10000));
  1098.           cE.save(EmployeeFile);
  1099.           cE.allClr();
  1100.           cE.load(EmployeeFile);
  1101.           cE.sort();
  1102.           cout << "\nEmployees in alphabetical order: "
  1103.                << endl;
  1104.           while (cE.nextAsg(&E))
  1105.                cout << E << endl;
  1106.           return 0;
  1107.      }
  1108.  
  1109. Instead of using:             we defined:    and:
  1110.  
  1111.      ITEM_NO_REL_OPS          CL_Dcmp()      ITEM_CMP_DEF
  1112.      ITEM_NO_ASSIGN           CL_Dassign()   ITEM_ASSIGN_DEF
  1113.      ITEM_NO_COPYINIT         CL_Dnew()      ITEM_COPYINIT_DEF
  1114.      ITEM_NO_DELETE           CL_Ddelete()   ITEM_DELETE_DEF
  1115.      ITEM_NO_STRM_INSERT      operator<<()   ITEM_STRM_INSERT_DEF
  1116.      ITEM_NO_STRM_EXTRACT     operator>>()   ITEM_STRM_EXTRACT_DEF
  1117.  
  1118. Notice that CL_Dcmp() isn't inlined since we need a compare
  1119. function pointer.  The rest are inlined since they will only be
  1120. expanded one time in the overridden virtual functions of the
  1121. container.
  1122.  
  1123. CL_Ddelete() isn't really necessary since the Employee structure
  1124. already has a destructor.
  1125.  
  1126. Notice that the stream insertion and extraction operators have
  1127. (ITEM **) pointer parameters.  This is to insure that these don't
  1128. collide with already existing operators.  For example, C++ string
  1129. pointers already have overloaded stream operators.  Cl.hpt/hpf
  1130. define the following string stream operators:
  1131.  
  1132.      inline ostream& operator<<(ostream& os, char ** D)
  1133.      {
  1134.           int len = (*D? strlen(*D) : 0);
  1135.           if ((os << len << endm))
  1136.                if (len)
  1137.                     os.write(*D,len);
  1138.           return os;
  1139.      }
  1140.  
  1141.      inline istream& operator>>(istream& is, char ** D)
  1142.      {
  1143.           int len;
  1144.           if (*D)  {
  1145.                delete *D;
  1146.                *D = (char *)0;
  1147.           }
  1148.           if ((is >> len >> nextm))
  1149.                if (len)
  1150.                  if ((*D = new char[len+1])
  1151.                     != (char *)0)  {
  1152.                     is.read(*D,len);
  1153.                     (*D)[len] = '\0';
  1154.                  }
  1155.                  else
  1156.                     is.ignore(len);
  1157.           return is;
  1158.      }
  1159.  
  1160. These operators store the length of the string ahead of the string
  1161. on the stream.  Contrast the string extraction operator with that
  1162. defined in iostream.h.  Our extraction operator allocates the
  1163. buffer while the standard extractor expects a sufficiently large
  1164. buffer.
  1165.  
  1166. Looking back at our example you now know the reason for passing the
  1167. address of the name pointer to the stream operators, i.e.
  1168.      ...  os << &((**E).name)  ...
  1169.  
  1170. and
  1171.  
  1172.      ...  is >> &((**E).name)  ...
  1173.  
  1174. Since strings will most likely appear often in your data types,
  1175. these special file stream string operators have been provided in
  1176. cl.hpt/hpf.
  1177.  
  1178.  
  1179. 4.0  Polymorphic Nodes
  1180.  
  1181. Thus far we have only considered binding homogeneous data.  While 
  1182. there is no reason why we can't bind heterogeneous data, it 
  1183. remains difficult to provide the full spectrum of Container Lite 
  1184. functionality owing to the complexities of having to provide a 
  1185. common assignment operator, copy initializer constructor, stream 
  1186. operators, etcetera.  Of course if this functionality isn't 
  1187. required the use of CL_DELETE_ONLY with either a template or 
  1188. "form template" suffices nicely.
  1189.  
  1190. In order for you to more readily construct polymorphic clusters of 
  1191. heterogeneous data the pitem.hpp/cpp files, supplied in the registered 
  1192. version, declares/defines two classes, Streamable and Mutual, either 
  1193. of which you can use as the polymorphic root for your family cluster 
  1194. of classes.  Streamable forms the foundation of any persistent cluster 
  1195. with run time typing, compatible assignment, cloning, and stream 
  1196. processing of the various cluster members.  Mutual is itself derived 
  1197. from Streamable and additionally provides for multiple reference 
  1198. arbitration in RAM as well as automatic reference resolution during 
  1199. streaming operations.  For example if an object with multiple 
  1200. references is saved on a stream only one copy is saved.  Upon 
  1201. reloading the multiple links are automatically reconstructed.
  1202.  
  1203. You can use either Streamable or Mutual in conjunction with Container 
  1204. Lite to form polymorphic tree and graph structures.  A pitem.cbk file 
  1205. is provided allowing you to cookbook your cluster derived classes.  It 
  1206. is fully commented with step by step instructions.
  1207.  
  1208.  
  1209. 5.0  Shareware Registration and Distribution
  1210.  
  1211. This version of Container Lite is licensed to you as shareware.  You 
  1212. may try out Container Lite for a reasonable length of time (60 days) 
  1213. to determine its fitness for your purposes.  If you decide to go on 
  1214. using Container Lite you must obtain a user's license by registering!  
  1215. You may also redistribute (share) the Container Lite shareware package 
  1216. as outlined below.  Please contact PSW for OEM licensing information.
  1217.  
  1218.  
  1219. 5.1  Registration
  1220.  
  1221. Registered users receive a full length (detailed reference) hard copy 
  1222. manual, polymorphic node files (pitem.hpp, pitem.cpp, pitem.cbk), and 
  1223. Container Lite source code broken down into its individual member 
  1224. functions and grouped together in appropriate files to facilitate the 
  1225. building of libraries (a makefile is also included).  Registered users 
  1226. are granted a users license that allows for the use of Container Lite 
  1227. in the development of software without royalty.
  1228.  
  1229. -----------------------------------------------------------
  1230.  
  1231. Container Lite v 1.82  Registration Form
  1232.  
  1233.  
  1234. Please specify        3.5" ____   or    5.25" ____
  1235. DOS formatted diskette
  1236.  
  1237.  
  1238. Name _____________________________________
  1239.  
  1240. Company __________________________________
  1241.  
  1242. Address __________________________________
  1243.  
  1244. City _______________________
  1245.  
  1246. State/Province _____________
  1247.  
  1248. Zip/Postal Code ____________
  1249.  
  1250. Country ____________________
  1251.  
  1252.  
  1253. Container Lite regular price:        $ 49.95
  1254. Summer introductory pricing if
  1255. ordered before Sept 21, 1993:        $ 20    ________
  1256.  
  1257.  
  1258. Shipping/Handling for U.S.        $  4
  1259. Foreign orders                $ 15    ________
  1260.  
  1261. Enclose U.S. bank draft (check)
  1262. or money order payable to
  1263. PSW / Power SoftWare  for:        total    ________
  1264.  
  1265. And mail to:
  1266.  
  1267. PSW / Power SoftWare
  1268. P.O. Box 10072
  1269. McLean, Virginia 22102 8072
  1270. U.S.A.
  1271. 703 759-3838
  1272.  
  1273. Sorry, we are not set up to accept credit card orders. 
  1274.  
  1275. -----------------------------------------------------------
  1276.  
  1277.  
  1278. 5.2  Distribution
  1279.  
  1280. The Container Lite shareware package consists of the following files:
  1281.  
  1282.     cl.doc        cl.hpp        cl.cpp
  1283.     cl.hpt        cl.hpf
  1284.  
  1285. Electronic Bulletin Board systems, including online commercial 
  1286. services, may distribute the Container Lite shareware package provided 
  1287. that the package remains intact and unaltered.
  1288.  
  1289. Commerical shareware distributors and computer user groups may also 
  1290. distribute the Container Lite shareware package provided that the 
  1291. package remains intact and unaltered in its own compressed file, 
  1292. subdirectory, or diskette.  Any associated literature (sales, 
  1293. promotional, or distribution) must also clearly explain the shareware 
  1294. concept in a conspicuous manner.
  1295.  
  1296.  
  1297. 6.0  Miscellaneous
  1298.  
  1299. I hope you like the Container Lite.  If you have any questions, 
  1300. comments, or suggestions, please don't hesitate to call me.  I always 
  1301. look forward to hearing from you.
  1302.  
  1303. Happy programming!
  1304. John Small
  1305. (voice) 703 759-3838
  1306.